<?php

/**
 * Archivo que describe la clase Node
 * @author Alejandro Gama <alejandro.gama@gmail.com>
 * @version 1.0
 * @package core
 * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
 */

/**
 * La clase abstracta Node es la base del framework
 *
 */
abstract class Node
{

    const GET = 0;
    const POST = 1;
    const RESOURCE = 0;
    const BLOCK = 1;

    public static $_METHOD = null;
    private static $_jsFiles = array( );
    private static $_jsMinMTime = 999999999;
    private static $_cssFiles = array( );
    private static $_cssMinMTime = 999999999;
    //MANTENGO UNA CACHE CON TODOS LOS NODOS CREADOS
    private static $_cache = array( );
    //Array con todos los Nodos obtenidos por el metodo GET
    //despuÃ©s del procesado del evento
    private static $_useds = array( );
    private static $_cachedNotUsed = array( );
    protected static $_prepared = false;
    private static $_group;
    static public $stage = 'meta';
    static private $_baseForm = '';
    static private $_traceNodes = array( );
    static private $_postRootTrace = '';
    static private $_crown;
    static private $_crownTrace;
    static protected $root = null;
    static private $rootChanged = null;
    //addChilds que se ejecutaron en el constructor del HTML
    protected $id;
    protected $classname;
    protected $_nodeId;
    protected $_idHex;
    //parent node
    private $parentNode = null;
    //child nodes
    private $childNodes = array( );
    //ruta desde este nodo hasta la raiz del arbol
    private $_trace;
    protected $errors = array( );

    //esta funcion tb evita que se metan los valores de las condiciones
    public function __set( $name, $value )
    {

        if ( substr( $name, 0, 3 ) === 'id[' && substr( $name, -1, 1 ) === ']' ) {
            if ( !is_array( $this->id ) ) $this->id = array( );

            $this->id[ ( int ) substr( $name, 3, -1 ) ] = $value;
        }
    }

    function __call( $m, $params )
    {//deberÃ­a pasarlos por referencia ya que la funcion puede tenerlos definidos por referencia
        //primero tengo que ver en que parte estoy
        if ( $params === null ) $params = array( );

        if ( strpos( $m, '_' ) === 0 ) {

            $method = substr( $m, 1 );

            if ( !isset( $_SESSION[ 'methods' ][ $this->_nodeId ] ) )
                    $_SESSION[ 'methods' ][ $this->_nodeId ] = array( );

            if ( !isset( $_SESSION[ 'methods' ][ $this->_nodeId ][ $this->_idHex ] ) )
                    $_SESSION[ 'methods' ][ $this->_nodeId ][ $this->_idHex ] = array(
                        );

            $_SESSION[ 'methods' ][ $this->_nodeId ][ $this->_idHex ][ $method ] = $params;
            /*
              if($method == 'loadNode'){
              var_dump($_SESSION['methods'][$this->_nodeId][$this->_idHex]);
              }
             */
            return call_user_func_array( array( $this, $method ), $params );
        }
    }

    /*
      function __sleep(){
      //solo guardo el id y la clase
      return array('id','_idHex','_nodeId');
      }

      function __wakeup(){
      //los meto en cache mientras no inicializo la clase Node
      if(!isset(self::$_cache[$this->_class]))
      self::$_cache[$this->_class]=array();

      if(!isset(self::$_cache[$this->_class][$this->_idHex]))
      self::$_cache[$this->_class][$this->_idHex]=$this;

      //$puntero=self::_getPointer($this->id,self::$_cache[$this->_class],true);
      //$puntero=$this;

      self::$_useds[]=$this;

      //self::$_session[]=$this;
      }
     */

    function handleGET( Event $e )
    {
        if ( $e->tag == 'head' ) {
            self::$stage = 'head';
            $e->cancelBubble();
        } else if ( $e->tag == 'body' ) {
            self::$stage = 'body';
            $e->cancelBubble();
        }
    }

    function fill()
    {
        
    }

    function handlePOST( Event $e )
    {
        
    }

    function doPOST( Event $e )
    {
        
    }

    final function setErrors( $errors )
    {
        $this->errors = $errors;
    }

    final function getId()
    {
        return $this->id;
    }

    final function addChild( $node )
    {
        if ( !self::$_prepared )
                throw new Exception( "No se pueden hacer addChild() dentro del procesamiento del evento" );

        $node->parentNode = $this;

        $this->childNodes[ ] = $node;
        return true;
    }

    final protected function reset()
    {
        if ( self::$_prepared )
                throw new Exception( "No se pueden hacer reset() fuera del procesamiento del evento" ); //si lo permitieramos tendriamos que alterar self::$_useds

        $_SESSION[ 'methods' ][ $this->_nodeId ][ $this->_idHex ] = array( );

        unset( self::$_cache[ $this->_nodeId ][ $this->_idHex ] );

        //hacemos una replica y la cambiamos por el actual nodo
        $replica = self::get( Config::$POLICY_NAME[ $this->_nodeId ], $this->id );

        $replica->childNodes = $this->childNodes;

        if ( $this == self::$root ) //pone una copia sin ejecutar los métodos en su lugar 
                self::$root = $replica;

        if ( $this == self::$_crown ) self::$_crown = $replica;

        if ( $this->parentNode != null )
                foreach ( $this->parentNode->childNodes as $i => $n )
                    if ( $n == $this )
                        $this->parentNode->childNodes[ $i ] = $replica;


        return $replica;
    }

    final protected function crownFormEvent( $tag )
    {
        return $this->formEvent( $tag ) . ($_SESSION[ 'AJAX' ] ? '<input type="hidden" name="_crwId" value="' . htmlspecialchars( $this->getCrownId() ) . '" />' : '');
    }

    final protected function formEvent( $tag )
    {
        return
                self::$_baseForm .
                '<input type="hidden" name="_trc" value="' . $this->_trace . '" />' .
                '<input type="hidden" name="_evt" value="' . htmlspecialchars( $tag ) . '" />';
    }

    final protected function crownUrlEvent( $tag, $params = null )
    {
        if ( $_SESSION[ 'AJAX' ] ) {
            $url = '_trc=' . $this->_trace . '&_evt=' . urlencode( $tag );
            if ( $params ) $url.='&' . http_build_query( $params );

            return '?' . $url . '" onclick="if(document.getElementById(\'' . $this->getCrownId() . '\')){ ajax(\'GET\',\'?' . $url . '&_crw=1\',ajax_replace_node); ajaxTransition(\'' . $this->getCrownId() . '\'); return false }';
        }
        else return $this->urlEvent( $tag, $params );
    }

    final protected function urlEvent( $tag, $params = null )
    {

        $url = '_trc=' . $this->_trace . '&_evt=' . urlencode( $tag );
        if ( $params ) $url.='&' . http_build_query( $params );

        return '?' . $url;
    }

    final protected function ajaxEvent( $method, $tag, $jscallback )
    {
        return 'ajax(\'' . $method . '\',\'?_trc=' . $this->_trace . '&_evt=' . urlencode( $tag ) . '\',' . $jscallback . ')';
    }

    final function getCrownId()
    {
        return 'n' . $this->_nodeId . 'i' . $this->_idHex;
    }

    final public function __toString()
    {
        $temp = Config::$TEMPLATES[ get_class( $this ) ];

        if ( !empty( $temp ) && isset( $temp[ self::$stage ] ) )
                include $temp[ self::$stage ];

        return '';
    }

    static final function _eventProcess( Event $e )
    {
        $oldRoot = self::$root;
        //tengo que seguir la traza desde el nodo más alto al más bajo y pasando el evento por ellos
        //TEGNO QUE GUARDAR EN $_crown EL NODO MÃ�S ELEVEDADO DE LA TRAZA QUE RECIBIMOS
//obtenemos de cache el nodo que produjo el evento
        //SI NO HAY EVENTO NO NECESITO SABER EL $crown????
//if( preg_match('/([0-9]{8}[A-Z]{1})/i',$_SERVER['SSL_CLIENT_S_DN'],$coincidencias) && $coincidencias[1]=='99999999Z' )
//	var_dump($e);

        if ( $e->tag !== null ) {


            foreach ( self::$_traceNodes as $n ) {


                $priv = self::get( $n[ 0 ], $n[ 1 ] ); //aqui no parece haber problema

                if ( !empty( $priv ) ) {

                    if ( self::$_crown == null ) {
                        self::$_crown = $priv;
                        $e->source = $priv;
                    }

                    if ( self::$_METHOD == self::POST ) {

                        $priv->setErrors( array( ) );

                        $priv->handlePOST( $e );

                        if ( empty( $priv->errors ) ) $priv->doPOST( $e );

                        else {
                            $priv->setErrors( $priv->errors );
                            $e->cancelBubble();
                        }
                    } else {
//			 if ($_SERVER['REMOTE_ADDR'] === '213.60.226.249')
//			        var_dump($priv);

                        $priv->handleGET( $e );
                    }

                    if ( self::$stage !== 'meta' ) { //POR CADA NODO QUE PASA EL EVENTO EL ES ROOT, HASTA QUE ALGUIEN CANCELE EL BUBBLE
                        self::$_crown = $priv;

                        //si no cancelo el bubble puedo modificar alguno de los estados de los nodos anteriores
                        //para que los eventos de la plantilla encuentren otros estados (aunque deberÃ­an ser los eventos de la plantilla
                        //los que hicieran ese trabajo) con lo que directamente cancelo

                        if ( self::$_METHOD == self::POST ) {

                            if ( empty( self::$_crown->errors ) ) {
                                unset( self::$_cache[ self::$_crown->_nodeId ][ self::$_crown->_idHex ] );
                                self::$_crown = self::get( $n[ 0 ], $n[ 1 ] );
                            }
                            else $e->bubble = false;
                        }

                        //NO DEBO CANCELARLO YO SINO EL NODO, DE ESTE MODO PUEDO REALIZAR EVENTOS A NODOS SUPERIORES CON AJAX!!!!!!!!!!!!!
                    }

                    unset( $priv ); //PERO ESTO NO LIBERA EL OBJETO

                    if ( !$e->bubble ) break;
                }
            }
        }

        self::$rootChanged = ($oldRoot != self::$root);
    }

    final public static function _prepareCrown()
    {

        if ( self::$_group !== User::getLogged()->getGroup() ) { //@TODO ESTO NO LO PRODRIA CAMBIAR POR EL wasReset()????
            self::$_cache = array( );

            if ( !empty( self::$_crown ) ) self::$_crown->reset();

            self::$_group = User::getLogged()->getGroup();
        }



        self::$_prepared = true;
        self::$_useds = array( self::$_crown );
        //self::$_useds[]=;

        for ( $i = 0; $i < count( self::$_useds ); $i++ ) {
            self::$_useds[ $i ]->childNodes = array( );
            self::$_useds[ $i ]->fill();
        }

        self::$_baseForm = '<input type="hidden" name="_tkn" value="' . User::getToken() . '" />';
        self::$_crown->_trace = self::$_crownTrace;

        foreach ( self::$_crown->childNodes as $c ) $c->_prepareTrace();
    }

    //A PARTIR DE AQUI NOOOOO PUEDEN CAMBIAR LOS ROLES
    final public static function _prepare()
    {

        if ( self::$_group !== User::getLogged()->getGroup() ) {
            self::$_cache = array( );

            if ( !empty( self::$root ) ) self::$root->reset();

            self::$_group = User::getLogged()->getGroup();
        }

        self::$_prepared = true;
        self::$_useds = array( );

        if ( empty( self::$root ) ) { //pero si está establecido en el evento //si he hecho un clear de root...?
            $ultimo = count( self::$_traceNodes ) - 1;
            self::$root = self::get( self::$_traceNodes[ $ultimo ][ 0 ],
                            self::$_traceNodes[ $ultimo ][ 1 ] );
        }
        else self::$_useds[ ] = self::$root;

        for ( $i = 0; $i < count( self::$_useds ); $i++ ) {
            self::$_useds[ $i ]->childNodes = array( );
            self::$_useds[ $i ]->fill();
        }

        self::$root->_prepareTrace();



        //ASIGNAR UN TOKEN A CADA ORPERACION CON EL NOMBRE DEL EVENTO Y UN RANDOM O DIRECTAMENTE UN RANDOM?
        self::$_baseForm = '<input type="hidden" name="_tkn" value="' . User::getToken() . '" />';
    }

    final protected function _prepareTrace()
    {
        if ( !empty( $this->parentNode ) )
                $this->_trace = $this->_nodeId . '-' . $this->_idHex . '/' . $this->parentNode->_trace;
        else $this->_trace = $this->_nodeId . '-' . $this->_idHex;

        foreach ( $this->childNodes as $c ) $c->_prepareTrace();
    }

    final public static function _unserialize( $trace )
    {
        self::$_crownTrace = $trace;

        if ( !isset( $_SESSION[ 'methods' ] ) )
                $_SESSION[ 'methods' ] = array( );

        if ( empty( $trace ) ) {
            self::$_traceNodes[ ] = array( DEFAULT_NODE, User::getLogged()->getId() );
            self::$_postRootTrace = Config::$POLICY_ID[ DEFAULT_NODE ] . '-' . self::_transID( User::getLogged()->getId() );
        } else {
            self::$_traceNodes = explode( '/', $trace );

            self::$_postRootTrace = self::$_traceNodes[ count( self::$_traceNodes ) - 1 ];

            $cid = null;
            $hnid = null;

            foreach ( self::$_traceNodes as &$n ) {
                if ( !empty( $n ) ) {
                    list($cid, $hnid) = explode( '-', $n );  //ESTO YA NO SERÁ EL NOMBRE DE UNA CLASE
                    $n = array( Config::$POLICY_NAME[ $cid ], self::_untransID( $hnid ) );
                }
            }

            unset( $cid, $hnid, $n );
        }
    }

    final static function _getRootTrace()
    {
        if ( self::$_METHOD == self::POST ) {
            if ( !self::$rootChanged ) return self::$_postRootTrace;
            else return self::$root->_nodeId . '-' . self::$root->_idHex;
            //return Config::$POLICY_ID[get_class(self::$root)].'-' .self::_transID(self::$root->getId());
            //aqui tend´ria que calcular la traza del root
        }
        else return self::$root->_trace;
    }

    final static function _getRoot()
    {
        return self::$root;
    }

    final static function _getCrown()
    {
        return self::$_crown;
    }

    final static function get( $nodetag, $id )
    {

        $nodeId = Config::$POLICY_ID[ $nodetag ];
        $idHex = self::_transID( $id );

        if ( isset( self::$_cache[ $nodeId ] ) && isset( self::$_cache[ $nodeId ][ $idHex ] ) ) {

            if ( self::$_prepared && isset( self::$_cachedNotUsed[ $nodeId ] ) && isset( self::$_cachedNotUsed[ $nodeId ][ $idHex ] ) ) {
                unset( self::$_cachedNotUsed[ $nodeId ][ $idHex ] );
                self::$_useds[ ] = self::$_cache[ $nodeId ][ $idHex ];
            }

            return self::$_cache[ $nodeId ][ $idHex ];
        }


        if ( Config::$POLICY_TYPE[ $nodeId ] == self::RESOURCE ) {

            list( $select, $from_where, $idNumeric, $end, $idCampo ) = SecurityCache::getResourceInfo( self::$_group,
                            $nodeId );

            $sql = 'SELECT ' . $select . $from_where;

            /*
              if (is_array($idCampo)) {
              $i=0;
              foreach ($idCampo as $c=>$isNumeric) {
              $sql.=' AND ' .Config::$helper->escapeIdentifier('id['.$i.']') . '=';

              if ($isNumeric)
              $sql.= Config::$helper->safe_num($id[$i]);
              else
              $sql.= Config::$helper->safe($id[$i]);

              $i++;
              }
              }
              else if ($idNumeric)
              $sql.=' AND id=' . Config::$helper->safe_num($id);
              else
              $sql.=' AND id=' . Config::$helper->safe($id);
             */

            if ( is_array( $id ) ) {
                $i = 0;

                foreach ( $idCampo as $c => $isNumeric ) {

                    $sql.=' AND ' . $c . '=';
                    if ( $isNumeric )
                            $sql.= Config::$helper->safe_num( $id[ $i ] );
                    else $sql.= Config::$helper->safe( $id[ $i ] );

                    $i++;
                }
            }
            else {

                foreach ( $idCampo as $c => $isNumeric ) {

                    $sql.=' AND ' . $c . '=';
                    if ( $isNumeric ) $sql.= Config::$helper->safe_num( $id );
                    else $sql.= Config::$helper->safe( $id );
                }
            }



            if ( !empty( $end ) ) $sql.=' ' . $end;

            // if( preg_match('/([0-9]{8}[A-Z]{1})/i',$_SERVER['SSL_CLIENT_S_DN'],$coincidencias) && $coincidencias[1]=='99999999R' )
            //                       echo $sql;
            //if ( self::$_METHOD == self::POST && !self::$_prepared && Config::$helper->isForUpdateAvailable() ) //solo lo que sea antes de la ejecución del método __prepare
            //    $sql.=' FOR UPDATE';


            $resultado = MyLink::$default->get_objects( $sql );

            unset( $sql, $select, $from_where, $idNumeric, $end );

            if ( !$resultado->current() ) {
                $resultado->free();

                return null;
            }

            //LA creación de una clse puede provocar el cambio de rol por lo que puede que sea otra clase la que se tenga que cargar

            $node = $resultado->current();
            $resultado->free();
        } else {
            $blockclass = SecurityCache::getBlockInfo( self::$_group, $nodeId );
            if ( empty( $blockclass ) ) return null;

            $node = new $blockclass;
            $node->id = $id;
        }

        $node->_idHex = $idHex;
        $node->_nodeId = $nodeId;


        if ( isset( $_SESSION[ 'methods' ][ $nodeId ] ) && isset( $_SESSION[ 'methods' ][ $nodeId ][ $idHex ] ) )
                foreach ( $_SESSION[ 'methods' ][ $nodeId ][ $idHex ] as $method =>
                        $params )
                    call_user_func_array( array( $node, $method ), $params );


        if ( !isset( self::$_cache[ $nodeId ] ) )
                self::$_cache[ $nodeId ] = array( );



        self::$_cache[ $nodeId ][ $idHex ] = $node;

        if ( self::$_prepared ) self::$_useds[ ] = $node;
        else self::$_cachedNotUsed[ $nodeId ][ $idHex ] = $node;

        return $node;
    }

    final static function getArray( $nodetag, &$datos, $where = null,
            $order = null, $num = null, $offset = null, $total = null )
    {
        $numtotal = 0;

        $nodeId = Config::$POLICY_ID[ $nodetag ];

        if ( Config::$POLICY_TYPE[ $nodeId ] == self::RESOURCE ) {

            list( $select, $from_where, $idNumeric, $end, $idCampo ) = SecurityCache::getResourceInfo( self::$_group,
                            $nodeId );

            if ( !empty( $where ) ) $from_where.=' AND (' . $where . ')';

            if ( !empty( $end ) ) $from_where.=' GROUP BY ' . $end;


            if ( !empty( $order ) ) $order = ' ORDER BY ' . $order;
            /*
              else if(is_array($idNumeric)){

              $order='ORDER BY '.Config::$helper->escapeIdentifier('id[0]');
              for($i=1;$i<count($idNumeric);$i++);
              $order.=','.Config::$helper->escapeIdentifier('id['.$i.']');

              }
              else
              $order=' ORDER BY id';
             */

            if ( !empty( $offset ) ) { //tiene que estar num tb?..no
                if ( Config::$helper->isLIMITAvailable() ) {

                    $sql = 'SELECT ' . $select . $from_where . $order;

                    if ( !empty( $num ) )
                            $sql.=' LIMIT ' . $offset . ',' . $num;
                }
                else if ( !empty( $num ) ) //SI NO HAY ORDEN NO TIENE SENTIDO LA PAGINACION
                        $sql = 'SELECT * FROM ( SELECT ' . $select . ',ROW_NUMBER() OVER (' . $order . ') AS R0wNumb3r ' . $from_where . ') AS P4g3dT4bl3 WHERE R0wNumb3r >' . $offset . ' AND R0wNumb3r <=' . ($offset + $num);
                //http://www.mssqltips.com/sqlservertip/2696/comparing-performance-for-different-sql-server-paging-methods
                //
                    //segun el blog anterior las optimizaciones de sql 2012 solo afectan a la primera pagina, o lo mismo que si fuera offset a 0 y TOP
            }
            else if ( !empty( $num ) ) {

                if ( Config::$helper->isLimitAvailable() )
                        $sql = 'SELECT ' . $select . $from_where . $order . ' LIMIT ' . $num;

                else if ( Config::$helper->isTopAvailable() )
                        $sql = 'SELECT TOP(' . $num . ') ' . $select . $from_where . $order;
                else
                        $sql = 'SELECT * FROM ( SELECT ' . $select . ',ROW_NUMBER() OVER (' . $order . ') AS R0wNumb3r ' . $from_where . ') AS P4g3dT4bl3 WHERE R0wNumb3r <=' . ($num);
            }
            else
                    $sql = 'SELECT ' . $select . $from_where . ( empty( $order ) ? '' : $order);

            //echo $sql;
            //if( preg_match('/([0-9]{8}[A-Z]{1})/i',$_SERVER['SSL_CLIENT_S_DN'],$coincidencias) && $coincidencias[1]=='99999999R' )
            //                       echo $sql;

            $calculeRows = empty( $total ) || ($total - 2 * $num) < $offset;

            //   if ($calculeRows)
            //       $sql = 'SQL_CALC_FOUND_ROWS ' . $sql;
            //   $sql = 'SELECT SQL_SMALL_RESULT ' . $sql;
//echo $sql;
            //if (self::$_METHOD == self::POST && !self::$_prepared)
            //    $sql.=' FOR UPDATE'; //solo lo que sea antes de la ejecución del método __prepare
            // $coincidencias=array();
            // if(preg_match('/\/SERIALNUMBER=(([0-9]{8}[A-Z]{1})|([LMXYZ]{1}[0-9]{7}[A-Z]{1}))/i',$_SERVER['SSL_CLIENT_S_DN'],$coincidencias) && $coincidencias[1]==='99999999R') //13131473Z
            //        echo $sql;
            ///ANTES DEL get-objects tengo que hacer un fetch row

            if ( $calculeRows ) {
                //$numtotal = MyLink::$default->get_rows('SELECT FOUND_ROWS()', true)->getField(0);
                if ( empty( $end ) )
                        $numtotal = MyLink::$default->get_rows( 'SELECT COUNT(*) ' . $from_where )->getField( 0 );
                else
                        $numtotal = MyLink::$default->get_rows( 'SELECT SUM(contador) FROM (SELECT 1 as contador ' . $from_where . ') AS T4BL3' )->getField( 0 );
            }
            else $numtotal = $total;

            $resultado = MyLink::$default->get_objects( $sql );
            if ( $resultado->numRows() === 0 ) return 0;

            if ( !isset( self::$_cache[ $nodeId ] ) )
                    self::$_cache[ $nodeId ] = array( );


            //no queremos llegar a la ultima página y que no exista o que existan más


            foreach ( $resultado as $r ) { //aqui se está haciendo implicitamente el fetch
                $idHex = self::_transID( $r->id );
                $r->_nodeId = $nodeId;
                $r->_idHex = $idHex;

                self::$_cache[ $nodeId ][ $idHex ] = $r;

                if ( self::$_prepared ) self::$_useds[ ] = $r;
                else self::$_cachedNotUsed[ $nodeId ][ $idHex ] = $r;

                if ( isset( $_SESSION[ 'methods' ][ $nodeId ] ) && isset( $_SESSION[ 'methods' ][ $nodeId ][ $idHex ] ) )
                        foreach ( $_SESSION[ 'methods' ][ $nodeId ][ $idHex ] as
                                $method => $params )
                            call_user_func_array( array( $r, $method ), $params );

                $datos[ ] = $r;
            }

            $resultado->free();
            return $numtotal;
        }

        return 0;
    }

    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////


    final public static function cacheCSS( $src )
    {
        self::$_cssFiles[ ] = $src;
        $mtime = filemtime( $src );

        if ( $mtime < self::$_cssMinMTime ) self::$_cssMinMTime = $mtime;
    }

    final public static function cacheJS( $src )
    {
        self::$_jsFiles[ ] = $src;
        $mtime = filemtime( $src );

        if ( $mtime < self::$_jsMinMTime ) self::$_jsMinMTime = $mtime;
    }

    final public static function _writeStyleScript()
    {
        if ( count( self::$_jsFiles ) > 0 ) {

            $name = md5( implode( '_', self::$_jsFiles ) );
            $file = 'cache' . DIRECTORY_SEPARATOR . $name;

            if ( !file_exists( $file ) || filemtime( $file ) > self::$_jsMinMTime ) {
                $content = '';
                foreach ( self::$_jsFiles as $f )
                        $content.="\n" . file_get_contents( $f );

                file_put_contents( $file, $content );
            }

            echo '<script type="text/javascript" src="cache.php?f=' . $name . '" ></script>';
        }


        if ( count( self::$_cssFiles ) > 0 ) {

            $name = md5( implode( '_', self::$_cssFiles ) );
            $file = 'cache' . DIRECTORY_SEPARATOR . $name;

            if ( !file_exists( $file ) || filemtime( $file ) > self::$_cssMinMTime ) {
                $content = '';
                foreach ( self::$_cssFiles as $f )
                        $content.="\n" . file_get_contents( $f );

                file_put_contents( $file, $content );
            }

            echo '<link rel="stylesheet" type="text/css" href="cache.php?f=' . $name . '" title="default" />';
        }
    }

    final public static function JSEncoding( $s, $unicode = true )
    {
        if ( $unicode ) {
            $s = mb_convert_encoding( $s, 'UCS-2', CHARSET );
            if ( empty( $s ) ) return '';

            return '\\u' . implode( '\\u', str_split( bin2hex( $s ), 4 ) );
        }
        else if ( empty( $s ) ) return '';
        else return '\\x' . implode( '\\x', str_split( bin2hex( $s ), 2 ) );
    }

    final static function output( $file, $attach = false, $expires = null )
    {
        //do something on download abort/finish
        //register_shutdown_function( 'function_name'  );
        if ( !file_exists( $file ) ) exit();

        $size = filesize( $file );
        $name = rawurlencode( basename( $file ) );

        @ob_end_clean(); /// decrease cpu usage extreme

        if ( $attach )
                header( 'Content-Disposition: attachment; filename="' . $name . '"' );
        else header( 'Content-Disposition: inline; filename="' . $name . '"' );

        if ( $expires === null ) {
            header( 'Expires: ' . gmdate( 'D, d M Y H:i:s',
                            time() + 7 * 24 * 60 * 60 ) . ' GMT' ); //una semana tienen
        } else {
            header( 'Cache-control: private, Max-Age=' . ($expires - time()) );
            header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', $expires ) . ' GMT' );
        }

        header( 'Accept-Ranges: bytes' );
        header( 'Cache-control: private' );
        header( 'Pragma: private' );

        $first_byte = 0;
        $last_byte = $size - 1;

        /////  multipart-download and resume-download
        //check if http_range is sent by browser (or download manager)
        if ( isset( $_SERVER[ 'HTTP_RANGE' ] ) ) {
            if ( preg_match( '/^bytes\s*=\s*(\d+)-(\d*)$/',
                            $_SERVER[ 'HTTP_RANGE' ], $matches ) ) {

                $first_byte = ( int ) $matches[ 0 ];
                if ( !empty( $matches[ 1 ] ) )
                        $last_byte = ( int ) $matches[ 1 ];

                header( 'HTTP/1.1 206 Partial Content' );
            }
        }

        $left_size = ($last_byte - $first_byte) + 1;
        header( 'Content-Range: bytes ' . $first_byte . '-' . $last_byte . '/' . $size );
        header( 'Content-Length: ' . $left_size );

        //open the file
        $fp = fopen( $file, 'rb' );
        //seek to start of missing part
        fseek( $fp, $first_byte );

        //start buffered download
        while ( !feof( $fp ) ) {
            //reset time limit for big files
            set_time_limit( 30 );
            print(fread( $fp, 1024 * 8 ) ); //bloques de 8Kb
            flush();
            ob_flush();
        }
        fclose( $fp );

        return true;
    }

    private final static function _transID( $id )
    {
        if ( is_array( $id ) ) {
            ksort( $id );

            foreach ( $id as &$i ) $i = b64url_encode( $i );

            return implode( '.', $id );
        }

        return b64url_encode( $id );
    }

    private final static function _untransID( $id )
    {

        if ( strpos( $id, '.' ) !== false ) {
            $id = explode( '.', $id );
            foreach ( $id as &$n ) $n = b64url_decode( $n );
            return $id;
        }

        return b64url_decode( $id );
    }

    final static function _initNode()
    {
        self::$_group = User::getLogged()->getGroup();

        if ( !isset( $_SESSION[ 'AJAX' ] ) ) $_SESSION[ 'AJAX' ] = AJAX;

        if ( isset( $_REQUEST[ '_crw' ] ) && $_SESSION[ 'AJAX' ] )
                self::$stage = 'ajax';
    }

}

if ( strtoupper( $_SERVER[ 'REQUEST_METHOD' ] ) === 'GET' )
        Node::$_METHOD = Node::GET;
else if ( strtoupper( $_SERVER[ 'REQUEST_METHOD' ] ) === 'POST' )
        Node::$_METHOD = Node::POST;

//register_shutdown_function(array('Node','_finishNode'));
?>
